package com.inepex.inechart.shared.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;

import com.inepex.inechart.client.Defaults;
import com.inepex.inechart.shared.misc.SearchParameter;
import com.inepex.inechart.shared.properties.Color;


@SuppressWarnings("rawtypes")
public abstract class KeyValueDataSet<E extends KeyValueDataEntry> extends AbstractDataSet {

	protected boolean sorted;
	protected boolean sortable = true;
	protected ArrayList<E> entries;

	protected E keyMin;
	protected E keyMax;
	protected E valueMin;
	protected E valueMax;

	protected Color color;
	protected String name;

	protected KeyValueDataSet() {
		this(Defaults.name + ++autotitleMaxIndex, colorSet.getNextColor());
	}

	protected KeyValueDataSet(String name, Color color) {
		entries = new ArrayList<E>();
		sorted = false;
		this.name = name;
		this.color = color;
	}

	public void addEntry(E entry){
		entry.container = this;
		entries.add(entry);
		sorted = false;
		checkExtremes(entry);
	}

	public void removeEntry(E entry){
		entries.remove(entry);
	}

	@Override
	public void clear() {
		entries.clear();
	}

	protected void findExtremes(){
		if(entries.size() >= 1){
			keyMax = keyMin = valueMax = valueMin = entries.get(0);
		}
		else{
			return;
		}
		for(E e : entries){
			checkExtremes(e);
		}
	}

	@SuppressWarnings("unchecked")
	protected void checkExtremes(E entry){
		if(keyMax == null || entry.getKey().compareTo(keyMax.getKey()) > 0){
			keyMax = entry;
		}
		if(keyMin == null || entry.getKey().compareTo(keyMin.getKey()) < 0){
			keyMin = entry;
		}
		if(valueMax == null || entry.getValue().doubleValue() > valueMax.getValue().doubleValue()){
			valueMax = entry;
		}
		if(valueMin == null || entry.getValue().doubleValue() < valueMin.getValue().doubleValue()){
			valueMin = entry;
		}
	}

	@SuppressWarnings("unchecked")
	protected void sortIf(){
		if(!sortable || sorted){
			return;
		}
		Collections.sort(this.entries);
		sorted = true;
	}

	public boolean isSortable() {
		return sortable;
	}

	public void setSortable(boolean sortable) {
		this.sortable = sortable;
	}

	public boolean isSorted() {
		return sorted;
	}

	public E getKeyMin() {
		return keyMin;
	}

	public E getKeyMax() {
		return keyMax;
	}

	public E getValueMin() {
		return valueMin;
	}

	public E getValueMax() {
		return valueMax;
	}

	public TreeMap<Double, Double> getXYMap(){
		sortIf();
		TreeMap<Double, Double> map = new TreeMap<Double, Double>();
		for(KeyValueDataEntry de : entries){
			double[] vp = de.getValuePair();
			map.put(vp[0], vp[1]);
		}
		return map;
	}

	public int indexOf(E entry){
		return entries.indexOf(entry);
	}

	public boolean containsEntry(KeyValueDataEntry e){
		return entries.contains(e);
	}

	public Color getColor() {
		return color;
	}

	public void setColor(Color color) {
		this.color = color;
	}

	public KeyValueDataEntry searchByKey(double x, SearchParameter searchParameter){
		int index = searchIndexByKey(x, searchParameter);
		if(index < 0){
			return null;
		}
		return entries.get(index);
	}
	
	public KeyValueDataEntry getEntry(int index){
		if(index < 0 || index >= entries.size()){
			return null;
		}
		return entries.get(index);
	}
	
	/**
	 * 
	 * @param x
	 * @param searchParameter
	 * @return
	 */
	public int searchIndexByKey(double x, SearchParameter searchParameter){
		sortIf();
		if(entries.size() == 0){
			return -1;
		}
		else if(entries.size() == 1){
			double[] firstVP = entries.get(0).getValuePair();
			switch(searchParameter){
			case exact:
				return x == firstVP[0] ? 0 : -1;
			case exact_or_higher:
				return x < firstVP[0] ? 0 : -1;
			case exact_or_lower:
				return x > firstVP[0] ? 0 : -1;
			default:
				return 0;
			}
		}
		int intervalStart = 0;
		int intervalEnd = entries.size() - 1;
		double[] startVP = entries.get(intervalStart).getValuePair();
		if(startVP[0] > x){
			if(searchParameter == SearchParameter.exact_or_lower){
				return -1;
			}
			else if(searchParameter != SearchParameter.exact){
				return 0;
			}
		}
		double[] endVP = entries.get(intervalEnd).getValuePair();
		if(endVP[0] < x){
			if(searchParameter == SearchParameter.exact_or_higher){
				return -1;
			}
			else if(searchParameter != SearchParameter.exact){
				return 1;
			}
		}
		while(intervalEnd - intervalStart > 1){
			int intervalMiddle = intervalStart +  (intervalEnd - intervalStart) / 2;
			double[] midVP = entries.get(intervalMiddle).getValuePair();
			if(x > midVP[0]){	
				intervalStart = intervalMiddle;
			}
			else if(x == midVP[0]){
				return intervalMiddle;
			}
			else{
				intervalEnd = intervalMiddle;
			}
		}
		startVP = entries.get(intervalStart).getValuePair();
		endVP = entries.get(intervalEnd).getValuePair();
		switch(searchParameter){
		case exact:
			return -1;
		case exact_or_higher:
			return intervalEnd;
		case exact_or_lower:
			return intervalStart;
		default:
			return Math.abs(endVP[0] - x) > Math.abs(startVP[0] - x) ?
					intervalStart : intervalEnd;
		}
	}

	/**
	 * [fromX,toX]
	 * @param fromX inclusive
	 * @param toX inclusive
	 * @return
	 */
	public List<E> getEntriesByKey(double fromX, double toX) {
		List<E> entrySubList = new ArrayList<E>();
		int fromI = searchIndexByKey(fromX, SearchParameter.exact_or_higher);
		if(fromI < 0){
			return entrySubList;
		}
		int toI = searchIndexByKey(toX, SearchParameter.exact_or_lower);
		return entries.subList(fromI, toI < entries.size() - 1 ? toI + 1 : toI);
	}

	public List<E> getEntries(){
		sortIf();
		return new ArrayList<E>(entries);
	}

	/**
	 * ]-inf,beforeX[
	 * @param beforeX exclusive
	 * @return
	 */
	public List<E> getEntriesBeforeKey(double beforeX){
		int i = searchIndexByKey(beforeX, SearchParameter.exact_or_higher);
		List<E> ret = new ArrayList<E>();
		if(i > 0){
			ret.addAll(entries.subList(0, i));
		}
		return ret;
	}
	
	/**
	 * ]afterX,+inf[
	 * @param afterX exclusive
	 * @return
	 */
	public List<E> getEntriesAfterKey(double afterX){
		int i = searchIndexByKey(afterX, SearchParameter.exact_or_higher);
		List<E> ret = new ArrayList<E>();
		if(i > 0){
			if(entries.get(i).getValuePair()[0] == afterX){ //exact match
				i++;
			}
			if(i < entries.size()){
				ret.addAll(entries.subList(i, entries.size()));
			}
		}
		return ret;
	}

	public static KeyValueDataEntry[] searchValueExtremes(Collection<KeyValueDataEntry> entries){
		if(entries.size() == 0){
			return null;
		}
		KeyValueDataEntry max = null, min = null;
		boolean first = true;
		for(KeyValueDataEntry entry : entries){
			if(first){
				min = max = entry;
				first = !first;
				continue;
			}
			if(entry.getValue().doubleValue() > max.getValue().doubleValue()){
				max = entry;
			}
			if(entry.getValue().doubleValue() < min.getValue().doubleValue()){
				min = entry;
			}
		}
		return new KeyValueDataEntry[]{min,max};
	}
}
